package com.zsj.util;

import com.zsj.entity.HttpReturnMessage;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.util.CollectionUtils;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * @Description:
 * @Author:
 * @Date:
 */
@Slf4j
public class HttpUtil {
    //连接超时时间
    private static final int DEFAULT_CONNECTION_TIMEOUT = 10000;
    //读数据超时时间
    private static final int DEFAULT_SOCKET_TIMEOUT = 10000;
    //连接池获取可用连接超时时间
    private static final int DEFAULT_CONNECTION_REQUEST_TIMEOUT = 5000;

    private static final int DEFAULT_MAX_CONNECTION = 200;
    private static final int DEFAULT_MAX_ROUTE_CONNECTION = 20;

    private static final RequestConfig defalutRequestCfg;
    private static final Object lock = new Object();

    private static HttpClientBuilder clientBuilder;


    static {
        // 默认配置，设默认的连接超时时间、请求超时时间、结果超时时间
        Builder reqCfg = RequestConfig.custom();
        reqCfg.setSocketTimeout(DEFAULT_SOCKET_TIMEOUT);
        reqCfg.setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT);
        reqCfg.setConnectionRequestTimeout(DEFAULT_CONNECTION_REQUEST_TIMEOUT);
        defalutRequestCfg = reqCfg.build();
    }

    /**
     * 发送Get请求，自定义所有参数<br>
     *
     * @param httpUrl
     * @param params
     * @param headers
     * @param readTimeout
     * @return
     * @throws Exception
     */
    public static HttpReturnMessage doGet(String httpUrl, Map<String, String> params, Map<String, String> headers,
                                          int connectTimeout, int readTimeout, boolean isOrignal) throws Exception {

        debug(httpUrl, params, headers, readTimeout, "get");

        // 构建Url
        URIBuilder url = new URIBuilder(httpUrl);
        url.setCharset(Charset.forName("UTF-8"));

        // 设置请求参数
        if (params != null) {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                url.addParameter(entry.getKey(), entry.getValue());
            }
        }

        HttpGet httpGet = new HttpGet(url.build());

        Builder cfg = RequestConfig.copy(defalutRequestCfg);
        // 设置读超时时间
        if (readTimeout > 0) {
            cfg.setSocketTimeout(readTimeout);
        }
        // 设置连接超时时间
        if (connectTimeout > 0) {
            cfg.setConnectTimeout(connectTimeout);
        }
        httpGet.setConfig(cfg.build());

        // 设置header
        if (headers != null) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                httpGet.addHeader(entry.getKey(), entry.getValue());
            }
        }

        // 执行请求
        HttpResponse response = getClient().execute(httpGet);
        if (isOrignal) {
            return getHttpBodyOrignal(response);
        } else {
            return getHttpBody(response);
        }
    }


    /**
     * 发送post请求,全部参数自定义
     *
     * @param url
     * @param params
     * @param headers
     * @param readTimeout
     * @return
     * @throws Exception
     */
    public static HttpReturnMessage doPost(String url, Map<String, String> params, Map<String, String> headers,
                                           int connectTimeout, int readTimeout) throws Exception {

        debug(url, params, headers, readTimeout, "post");

        HttpPost httpPost = new HttpPost(url);
        // 设置请求参数
        if (params != null) {
            List<NameValuePair> values = getNameValuePairList(params);
            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(values, Charset.forName("UTF-8"));
            httpPost.setEntity(entity);
        }

        Builder cfg = RequestConfig.copy(defalutRequestCfg);

        // 设置读超时时间
        if (readTimeout > 0) {
            cfg.setSocketTimeout(readTimeout);

        }
        // 设置连接超时时间
        if (connectTimeout > 0) {
            cfg.setConnectTimeout(connectTimeout);
        }

        httpPost.setConfig(cfg.build());
        // 设置header
        if (headers != null) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                httpPost.addHeader(entry.getKey(), entry.getValue());
            }
        }

        // 执行请求
        HttpResponse response = getClient().execute(httpPost);
        return getHttpBody(response);
    }

    public static HttpReturnMessage doPost(String url, String json, Map<String, String> headers,
                                           int connectTimeout, int readTimeout) throws Exception {

        debug(url, null, headers, readTimeout, "post");

        HttpPost httpPost = new HttpPost(url);
        // 设置请求参数
        if (json != null) {
            httpPost.setEntity(new StringEntity(json, "utf-8"));
        }

        Builder cfg = RequestConfig.copy(defalutRequestCfg);
        // 设置读超时时间
        if (readTimeout > 0) {
            cfg.setSocketTimeout(readTimeout);
        }
        // 设置连接超时时间
        if (connectTimeout > 0) {
            cfg.setConnectTimeout(connectTimeout);
        }
        httpPost.setConfig(cfg.build());

        // 设置header
        if (headers != null) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                httpPost.addHeader(entry.getKey(), entry.getValue());
            }
        }

        // 执行请求
        HttpResponse response = getClient().execute(httpPost);
        return getHttpBody(response);
    }

    public static HttpReturnMessage doPostBytes(String url, String contentType, Map<String, byte[]> bytes, Map<String
            , String> params, Map<String, String> headers, int connectionTimeout, int readTimeout) throws Exception {


        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        String uuid = UUID.randomUUID().toString();
        builder.setBoundary(uuid);
        builder.setCharset(Charset.forName("UTF-8"));

        ContentType ctype = ContentType.create(contentType, Charset.forName("UTF-8"));

        params.forEach((k, v) -> {
            StringBody stringBody = new StringBody(v, ctype);
            builder.addPart(k, stringBody);
        });

        bytes.forEach((k, v) -> builder.addBinaryBody(k, v, ContentType.APPLICATION_OCTET_STREAM, uuid));

        // 实例化客户端
        HttpPost post = new HttpPost(url);
        if (!CollectionUtils.isEmpty(headers)) {
            for (String key : headers.keySet()) {
                post.addHeader(key, headers.get(key));
            }
        }
        Builder reqCfg = RequestConfig.copy(defalutRequestCfg);
        // 设置读超时时间
        if (readTimeout > 0) {
            reqCfg.setSocketTimeout(readTimeout);
        }
        // 设置读超时时间
        if (connectionTimeout > 0) {
            reqCfg.setConnectTimeout(connectionTimeout);
        }
        post.setConfig(reqCfg.build());
        // 发送
        post.setEntity(builder.build());

        return getHttpBody(getClient().execute(post));
    }


    private static HttpClientBuilder getClientBuilder() {
        if (clientBuilder != null) {
            return clientBuilder;
        }

        synchronized (lock) {
            if (clientBuilder != null) {
                return clientBuilder;
            }
            // 实例化manager
            RegistryBuilder<ConnectionSocketFactory> registBuilder = RegistryBuilder.<ConnectionSocketFactory>create();
            LayeredConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactory.getSocketFactory();
            registBuilder.register("http", PlainConnectionSocketFactory.INSTANCE)
                    .register("https", sslSocketFactory);

            PoolingHttpClientConnectionManager conMgr = new PoolingHttpClientConnectionManager(registBuilder.build());
            conMgr.setMaxTotal(DEFAULT_MAX_CONNECTION);
            // 一个 Host 下，默认最多 20 个连接
            conMgr.setDefaultMaxPerRoute(DEFAULT_MAX_ROUTE_CONNECTION);
            // End Modify

            // 设置配置
            HttpClientBuilder cBuilder = HttpClientBuilder.create();
            cBuilder.setConnectionManager(conMgr);
            cBuilder.setDefaultRequestConfig(defalutRequestCfg);
            cBuilder.setRedirectStrategy(new LaxRedirectStrategy()).build();
            clientBuilder = cBuilder;
        }
        return clientBuilder;
    }


    /**
     * 从pool中获取一个HttpClient对象来执行Get/Post/Delete/Put等方法
     *
     * @return
     * @throws Exception
     */
    private static HttpClient getClient() throws Exception {
        // 实例化客户端
        CloseableHttpClient client = getClientBuilder().build();
        return client;
    }

    /**
     * 获取HttpBody
     *
     * @param resp
     * @return
     * @throws Exception
     */
    private static HttpReturnMessage getHttpBody(HttpResponse resp) throws Exception {

        int code = resp.getStatusLine().getStatusCode();
        HttpReturnMessage hrm = new HttpReturnMessage(code);
        HttpEntity rs = null;

        try {
            rs = resp.getEntity();
            Header[] headers = resp.getAllHeaders();
            hrm.setHeaders(headers);
            String content = EntityUtils.toString(rs, "UTF-8");
            hrm.setResult(content);
        } catch (Exception e) {

        } finally {
            // 关闭流
            if (null != rs) {
                EntityUtils.consume(rs);
            }
        }

        return hrm;
    }

    /**
     * 获取HttpBody
     *
     * @param resp
     * @return
     * @throws Exception
     */
    private static HttpReturnMessage getHttpBodyOrignal(HttpResponse resp) throws Exception {
        int code = resp.getStatusLine().getStatusCode();
        HttpReturnMessage hrm = new HttpReturnMessage(code);
        HttpEntity rs = null;
        ByteArrayOutputStream buffer = null;
        try {
            rs = resp.getEntity();
            Header[] headers = resp.getAllHeaders();
            hrm.setHeaders(headers);
            InputStream inputStream = rs.getContent();
            buffer = new ByteArrayOutputStream();
            int nRead;
            byte[] data = new byte[4096];
            while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
                buffer.write(data, 0, nRead);
            }
            byte[] byt = buffer.toByteArray();
            hrm.setFileByte(byt);
        } catch (Exception e) {
            log.error("http getHttpBodyOrignal error");
        } finally {
            // 关闭流
            if (null != buffer) {
                buffer.close();
            }
            if (null != rs) {
                EntityUtils.consume(rs);
            }
        }
        return hrm;
    }

    private static List<NameValuePair> getNameValuePairList(Map<String, String> params) {
        List<NameValuePair> listParam = new ArrayList<NameValuePair>();
        for (String key : params.keySet()) {
            listParam.add(new BasicNameValuePair(key, params.get(key)));
        }
        return listParam;
    }

    /**
     * 调试输出参数
     *
     * @param httpUrl
     * @param params
     * @param readTimeout
     */
    private static void debug(String httpUrl, Map<String, String> params, Map<String, String> headers, int readTimeout,
                              String prefix) {
        if (!log.isDebugEnabled()) {
            return;
        }
        int t = readTimeout;
        if (t < 0) {
            t = DEFAULT_SOCKET_TIMEOUT;
        }
        String msg = String.format("%s : Url = [ %s ] ,params = [ %s ] ,headers = [ %s ] ,timeout = [ %d ]", prefix,
                httpUrl, params, headers, t);
        log.debug(msg);
    }

}
